package ru.batrdmi.svnplugin;

import com.mxgraph.model.mxCell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxRectangle;
import org.jetbrains.annotations.Nullable;
import ru.batrdmi.svnplugin.logic.Revision;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class RevisionHighlighter extends MouseAdapter {
    public enum State {OFF, ON, PARTIAL}

    private static final String HIGHLIGHTED_PROPERTY = "highlighted";

    private final mxGraphComponent gc;
    private mxCell highlightedEdge;

    public RevisionHighlighter(mxGraphComponent gc) {
        this.gc = gc;
    }

    @Override
    public void mouseExited(MouseEvent e) {
        mouseOver(null);
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        mxCell cell = (mxCell) gc.getCellAt(e.getX(), e.getY());
        mouseOver(cell);
    }
    
    private void mouseOver(@Nullable mxCell cell) {
        RevisionGraphModel model = (RevisionGraphModel) gc.getGraph();
        Set<mxCell> cellsToRepaint = new HashSet<mxCell>();
        if (cell != highlightedEdge) {
            if (highlightedEdge != null) {
                cellsToRepaint.addAll(highlightEdge(model, highlightedEdge, false));
            }
            if (cell != null && isHighlightable(cell)) {
                highlightedEdge = cell;
                cellsToRepaint.addAll(highlightEdge(model, highlightedEdge, true));
            } else {
                highlightedEdge = null;
            }
        }
        if (!cellsToRepaint.isEmpty()) {
            mxRectangle bounds = model.getPaintBounds(cellsToRepaint.toArray());
            model.repaint(bounds);
        }
    }

    private static boolean isHighlightable(mxCell cell) {
        return cell.isEdge() && !cell.getStyle().equals(SVNRevisionGraph.IN_BRANCH_LINK_STYLE);
    }

    private static Set<mxCell> highlightEdge(RevisionGraphModel model, mxCell edge, boolean on) {
        Set<mxCell> changedCells = new HashSet<mxCell>();
        Revision targetRev = ((RevisionGraphModel.Node)edge.getTarget().getValue()).revisions.get(0);
        List<Revision> copiedOrMergedRevisions = targetRev.getCopiedOrMergedRevisions();
        for (Revision r : copiedOrMergedRevisions) {
            mxCell cell = model.getCellForRevision(r);
            if (cell != null) {
                changedCells.add(cell);
            }
        }
        for (mxCell cell : changedCells) {
            List<Revision> cellRevs = ((RevisionGraphModel.Node)cell.getValue()).revisions;
            highlightCell(cell, !on ? State.OFF : copiedOrMergedRevisions.containsAll(cellRevs) ? State.ON : State.PARTIAL);
        }

        highlightCell(edge, !on ? State.OFF : State.ON);
        changedCells.add(edge);

        return changedCells;
    }

    public static State getState(mxCell cell) {
        Object state = ((RevisionGraphModel.Node) cell.getValue()).properties.get(HIGHLIGHTED_PROPERTY);
        return (state instanceof State) ? (State) state : State.OFF;
    }

    private static void highlightCell(mxCell cell, State state) {
        Map<String, Object> props = ((RevisionGraphModel.Node) cell.getValue()).properties;
        if (state == State.OFF) {
            props.remove(HIGHLIGHTED_PROPERTY);
        } else {
            props.put(HIGHLIGHTED_PROPERTY, state);
        }
    }

}
